﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "GmMoveSelectionCmd.h"
#include "GbmpUiPlatformCommandIds.h"
#include "CmdStatusUtils.h"
#include "IUserTransaction.h"
#include "IModelView.h"
#include "Vector3dUtils.h"
#include "ILine3d.h"
#include "UiCommonDialog.h"
#include "IMainWindow.h"
#include "IUiView.h"
#include "IPlane.h"
#include "UiViewUtils.h"
#include "GbmpUiPlatformCommandRegister.h"
#include "ISelection.h"
#include "IGraphicsNodeReference.h"
#include "GbmpMoveUtils.h"
#include "IElementTransformationComponent.h"
#include "IActionInputEventArgs.h"
#include "GbmpWallUtils.h"
#include "GbmpBeamUtils.h"
#include "IDocument.h"
#include "INotificationDetailItem.h"
#include "INotificationManager.h"
#include "INotification.h"
#include "ICheckBoxGroupDefinition.h"
#include "UiDocumentViewUtils.h"
#include "GUiBaseInterfaceEnums.h"
#include "IGenericElement.h"
#include "IDrawingTable.h"
#include "ActionDrawStraightElementCreator.h"


#include "GcmpPreviewHelper.h"
#include "IGraphicsElementShape.h"
#include "IUiDocumentViewManager.h"
#include "IElement.h"
#include "IElementBasicInformation.h"
#include "GcmpBuiltInCategoryUniIdentities.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

using namespace gcmp;

const std::wstring KeepJoiningWhileMove = L"KeepJoiningWhileMove";
const std::wstring MoveCheckBoxGroup = L"MoveCheckBoxGroup";

namespace
{
    bool CanElementBeMoved(IDocument* pDoc, ElementId elementId)
    {
        const IElement* pElement = pDoc->GetElement(elementId);
        if (!pElement)
            return false;

        const IElementTransformationComponent *pElementTransformationComponent = pElement->GetElementTransformationComponent();
        if (!pElementTransformationComponent)
            return false;

        std::wstring cannotMovedExplanation = L"";
        return pElementTransformationComponent->CanBeMoved(&cannotMovedExplanation);
    }

    bool MoveElements(IDocument* pDoc, const std::vector<ElementId>& elementIdsToBeMoved, const Vector3d& originPosition, const Vector3d& moveDir, bool keepJoining, const IModelView* pModelView, std::wstring& strError)
    {
        if (elementIdsToBeMoved.size() == 1 && keepJoining)
        {
            ElementId elementId = *elementIdsToBeMoved.begin();
            if (CanElementBeMoved(pDoc, elementId))
            {
                IElement* pElement = pDoc->GetElement(elementId);
                if (!pElement)
                    return false;

                bool bRet = GbmpMoveUtils::MoveElementWithJoining(pElement, originPosition, moveDir, pModelView, strError);
                if (bRet == false && !strError.empty())
                {
                    std::vector<OwnerPtr<INotificationDetailItem>> detailItems;
                    INotificationManager::Get()->AddNotification(TransferOwnership(INotification::CreateNotification(NotificationTypes::WarningNotificationType, strError, std::move(detailItems))));
                }
            }
        }
        else
        {
            std::vector<ElementId> elementsCanBeMoved;
            FOR_EACH(elementId, elementIdsToBeMoved)
            {
                if (CanElementBeMoved(pDoc, elementId))
                {
                    elementsCanBeMoved.emplace_back(elementId);
                }
            }

            GbmpMoveUtils::MoveElements(dynamic_cast<IDocument*>(pDoc), elementsCanBeMoved, originPosition, moveDir, pModelView);
        }

        return true;
    }
}

GmMoveSelectionCmd::GmMoveSelectionCmd(void)
    : GbmpCommandBase(ID_CMD_GBMP_MOVE_SELECTION)
    , m_keepJoining(false)
{
}

GmMoveSelectionCmd::~GmMoveSelectionCmd(void)
{
}

gcmp::OwnerPtr<IAction> GmMoveSelectionCmd::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    const std::set<OwnerPtr<IGraphicsNodeReference>, OwnerPtrContainerUtil::LessPredicate<IGraphicsNodeReference>>& selectedItems = ISelection::Get()->GetGraphicsNodeReferences();

    if (selectedItems.size() == 0)
    {
        std::vector<OwnerPtr<INotificationDetailItem>> detailItems;
        INotificationManager::Get()->AddNotification(TransferOwnership(INotification::CreateNotification(NotificationTypes::InfomationNotificationType, GBMP_TR(L"请先选中对象，再进行移动操作。"), std::move(detailItems))));
        return nullptr;
    }
    
    

    IUiDocumentViewManager* pUiDocViewMgr = IUiDocumentViewManager::Get();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pUiDocViewMgr, L"pUiDocViewMgr为空",L"GDMPLab",L"2024-03-30");
    IUiView* pCurrentView = pUiDocViewMgr->GetCurrentUiView();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pCurrentView, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
    std::vector<IElement*> operationElements;
    m_opPreviewHelper = NEW_AS_OWNER_PTR(PreviewHelper, pCurrentView, operationElements);

    ActionDrawStraightElementInput actionInput;
    actionInput.LineFinishedCallback = actionInput.CreateOnLineFinishedCallback(&GmMoveSelectionCmd::OnLineFinished, this);
    actionInput.PromptMessageCallback = actionInput.CreateDefinePomptMessageCallback(&GmMoveSelectionCmd::DefinePromptMsg, this);
    actionInput.CoincideBehaviorCallback = actionInput.CreateDefineCoincideBehaviorCallback(&GmMoveSelectionCmd::DefineCoincideBehavior, this);
    actionInput.InputDialogCallback = actionInput.CreateDefineInputDialogCallback(&GmMoveSelectionCmd::DefineInputDialog, this);
    actionInput.InputEventCallback = actionInput.CreateOnActionInputEventCallback(&GmMoveSelectionCmd::OnActionInputEvent, this);
    actionInput.PreviewGraphicsShapeCallback = actionInput.CreateGetPreviewGraphicsShapeCallback(&GmMoveSelectionCmd::GetPreviewGraphicsShape, this);

    return ActionDrawStraightElementCreator::Create(actionInput);
}

bool GmMoveSelectionCmd::IsVisible() const
{
    CMD_ONLY_VISIBLE_IN_DOCUMENT_ENVIRONMENT
}

bool GmMoveSelectionCmd::IsEnabled() const
{
    CMD_ONLY_ENABLE_IN_DOCUMENT_ENVIRONMENT
}

gcmp::OwnerPtr<gcmp::IGraphicsElementShape> GmMoveSelectionCmd::GetPreviewGraphicsShape(const gcmp::Vector3d &moveVector)
{
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(m_opPreviewHelper, L"m_opPreviewHelper为空",L"GDMPLab",L"2024-03-30");
    return TransferOwnership(m_opPreviewHelper->GetMoveTempGRep(moveVector));
}

std::vector<ElementId> GmMoveSelectionCmd::RemoveTableGraphics(std::vector<ElementId> &elementIds)
{  
    std::set<ElementId> graphicsSet;
    FOR_EACH(oEntID, elementIds)
    {
        IDocument* pDoc = UiDocumentViewUtils::GetCurrentDocument();
        if (pDoc == nullptr)
            continue;

        IElement* pElement = pDoc->GetElement(oEntID);
        if (pDoc == nullptr)
            continue;

        IGenericElement* pGenericElement = quick_cast<IGenericElement>(pElement);
        if (pGenericElement == nullptr)
            continue;

        IDrawingTable* pTableElement = quick_cast<IDrawingTable>(pGenericElement->GetExternalObject());
        if (pTableElement == nullptr)
            continue;

        const std::set<ElementId> ids = pTableElement->GetRelatedElementIds();
        if (!ids.empty())
        {
            graphicsSet.insert(ids.begin(), ids.end());  
        }
    }
    
    std::vector<ElementId> result;
    FOR_EACH(oEntID, elementIds)
    {
        auto itor = graphicsSet.find(oEntID);
        if (itor == graphicsSet.end())
        {
            result.emplace_back(oEntID);
        }
    }

    return result;
}

ElementId GmMoveSelectionCmd::OnLineFinished(gcmp::IUiView* pCurrentView, const gcmp::Vector3d& startPt, const gcmp::Vector3d& endPt,
    const gcmp::ISnap* pStartSnap, const gcmp::ISnap* pEndSnap)
{
    DBG_WARN_AND_RETURN_UNLESS(pCurrentView, ElementId::InvalidID, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
    IModelView* pModelView = pCurrentView->GetModelView();

    const std::set<OwnerPtr<IGraphicsNodeReference>, OwnerPtrContainerUtil::LessPredicate<IGraphicsNodeReference>>& selectedItems = ISelection::Get()->GetGraphicsNodeReferences();
    std::vector<ElementId> elementIdsToBeMoved;
    elementIdsToBeMoved.reserve(selectedItems.size());
    FOR_EACH(selectedItem, selectedItems)
    {
        elementIdsToBeMoved.push_back(selectedItem->GetElementId());
    }

    //排除表格选中时，其中包含的图例，避免移动两次
    //因为表格本身会移动图例一次
    elementIdsToBeMoved = RemoveTableGraphics(elementIdsToBeMoved);

    IDocument* pDoc = UiDocumentViewUtils::GetCurrentDocument();
    DBG_WARN_AND_RETURN_UNLESS(pDoc, ElementId::InvalidID, L"doc为空",L"GDMPLab",L"2024-03-30");
    std::wstring strError;
    const Vector3d moveDir = endPt - startPt;

    OwnerPtr<IUserTransaction> opTransaction = IUserTransaction::Create(pDoc, GBMP_TR(L"移动"), true);
    DBG_WARN_AND_RETURN_UNLESS(opTransaction.get(), ElementId::InvalidID, L"opTransaction为空",L"GDMPLab",L"2024-03-30");

    if (MoveElements(pDoc, elementIdsToBeMoved, startPt, moveDir, m_keepJoining, pModelView, strError))
    {
        opTransaction->Commit();
    }
    else
    {
        opTransaction->Rollback();
        UiCommonDialog::ShowMessageBox(GBMP_TR(L"移动"), strError, (int)UiCommonDialog::ButtonType::OK);
    }

    std::vector<gcmp::IElement*> operationElements;
    //const GraphicsNodeReferenceOwnerPtrSet& tempselectedItems = ISelection::Get()->GetGraphicsNodeReferences();
    FOR_EACH(it, selectedItems)
    {
        const ElementId elementId = it->GetElementId();
        IElement *pElement = pDoc->GetElement(elementId);
        if (pElement != nullptr)
        {
            IElementBasicInformation* pBasicInformation = pElement->GetBasicInformation();
            if (pBasicInformation)
            {
                if (pBasicInformation->GetCategoryUid() != BuiltInCategoryUniIdentities::BICU_DRAWING_TABLE_ELEMENT && pBasicInformation->GetClassficationUid() != GuidUtils::FromString(L"{EDBC1DCC-7BC6-4EE1-BD90-0880B2E3E5C8}"))
                    operationElements.emplace_back(pElement);
                else
                {
                    IGenericElement* pGenericElement = quick_cast<IGenericElement>(pElement);
                    if (pGenericElement)
                    {
                        if (IDrawingTable* pTableElement = quick_cast<IDrawingTable>(pGenericElement->GetExternalObject()))
                        {
                            operationElements.emplace_back(pElement);
                            std::set<ElementId> items = pTableElement->GetTableItemIds();
                            FOR_EACH(item, items)
                            {
                                IElement* pTableItem = pDoc->GetElement(item);
                                if (pTableItem)
                                    operationElements.emplace_back(pTableItem);
                            }
                        }
                    }
                }
            }
        }
    }
    m_opPreviewHelper = NEW_AS_OWNER_PTR(gcmp::PreviewHelper, pCurrentView, operationElements);
    IMainWindow * mainwnd = IMainWindow::GetMainWindow();
    if(nullptr != mainwnd)
        mainwnd->UpdateControlStatus();
    return ElementId::InvalidID;
}

const std::wstring GmMoveSelectionCmd::DefinePromptMsg(DrawLineStatus drawLineStatus)
{
    switch (drawLineStatus)
    {
    case DrawLineStatus::Getting_StartPoint:
        return GBMP_TR(L"移动选择的物体 - 请输入起始点");
    case DrawLineStatus::Getting_EndPoint:
        return GBMP_TR(L"移动选择的物体 - 请输入终止点");
    default:
        return L"";
    }
}

bool GmMoveSelectionCmd::DefineCoincideBehavior(gcmp::IUiView* pCurrentView)
{
    std::vector<OwnerPtr<INotificationDetailItem>> detailItems;
    if (!IsWorldRayParallelToWorkPlane(pCurrentView))
    {
        INotificationManager::Get()->AddNotification(TransferOwnership(INotification::CreateNotification(NotificationTypes::InfomationNotificationType, GBMP_TR(L"起始点与终止点重合，对象仍在原位。"), std::move(detailItems))));
    }
    else
    {
        INotificationManager::Get()->AddNotification(TransferOwnership(INotification::CreateNotification(NotificationTypes::InfomationNotificationType, GBMP_TR(L"暂不支持在三维视图中的立面方向移动物体，请进入相应的立面图中进行编辑。"), std::move(detailItems))));
    }

    // 不结束Action，继续执行后续动作
    return false;
}

bool GmMoveSelectionCmd::IsWorldRayParallelToWorkPlane(gcmp::IUiView* pCurrentView)
{
    DBG_WARN_AND_RETURN_UNLESS(pCurrentView != nullptr, false, L"视图指针无效",L"GDMPLab",L"2024-03-30");
    IModelView* pModelView = pCurrentView->GetModelView();
    DBG_WARN_AND_RETURN_UNLESS(pModelView != nullptr, false, L"ModelView无效",L"GDMPLab",L"2024-03-30");

    auto ray = UiViewUtility::WorldPositionToWorldRay(pCurrentView, Vector3d::Zero);
    OwnerPtr<IPlane> pPlane = pModelView->GetWorkPlane();
    DBG_WARN_AND_RETURN_UNLESS(pPlane != nullptr, false, L"工作平面无效",L"GDMPLab",L"2024-03-30");

    if (Vector3dUtils::IsPerpendicular(ray->GetDirection(), pPlane->GetNormal()))
    {
        return true;
    }

    return false;
}

void GmMoveSelectionCmd::DefineInputDialog(DefineInputDialogParams& inputItems)
{
    IDocument* pDoc = UiDocumentViewUtils::GetCurrentDocument();
    DBG_WARN_AND_RETURN_VOID_UNLESS(pDoc, L"文档为空",L"GDMPLab",L"2024-03-30");

    const std::set<OwnerPtr<IGraphicsNodeReference>, OwnerPtrContainerUtil::LessPredicate<IGraphicsNodeReference>>& selectedItems = ISelection::Get()->GetGraphicsNodeReferences();
    // 如果多选， 这没有InputDialog
    if (selectedItems.size() > 1 || selectedItems.empty())
    {
        return;
    }

    ElementId elementId = (*selectedItems.begin())->GetElementId();
    IElement* pElement = pDoc->GetElement(elementId);
    if (GbmpWallUtils::IsWall(pElement) ||
        GbmpBeamUtils::IsBeam(pElement))
    {
        inputItems.title = GBMP_TR(L"移动");

        OwnerPtr<ICheckBoxGroupDefinition> opCheckBoxGroupDefinition = ICheckBoxGroupDefinition::Create(MoveCheckBoxGroup, GBMP_TR(L"移动"));
        DBG_WARN_AND_RETURN_VOID_UNLESS(opCheckBoxGroupDefinition, L"opCheckBoxGroupDefinition为空",L"GDMPLab",L"2024-03-30");

        m_keepJoining = true;
        bool isOk = opCheckBoxGroupDefinition->AddCheckBox(KeepJoiningWhileMove, GBMP_TR(L"保持端部连接约束"), GBMP_TR(L"保持端部连接约束"), CheckState::Checked);
        DBG_WARN_AND_RETURN_VOID_UNLESS(isOk, L"添加checkbox失败",L"GDMPLab",L"2024-03-30");

        inputItems.inputItems.push_back(TransferOwnership(opCheckBoxGroupDefinition));
    }
}

void GmMoveSelectionCmd::OnActionInputEvent(gcmp::IActionInputEventArgs* pArgs)
{
    DBG_WARN_AND_RETURN_VOID_UNLESS(pArgs, L"pArgs为空",L"GDMPLab",L"2024-03-30");
    const std::pair<std::wstring, gcmp::Any>& msg = pArgs->GetInputMessage();
    const std::wstring eventID = msg.first;
    gcmp::Any inputValue = msg.second;
    if (eventID == MoveCheckBoxGroup + L"_" + KeepJoiningWhileMove)
    {
        m_keepJoining = inputValue.cast<bool>();
    }
}

REGISTER_GM_COMMAND(GmMoveSelectionCmd)
